package app

import (
	"encoding/json"
	"github.com/google/uuid"
	"github.com/gorilla/websocket"
	"go.uber.org/fx"
	"sync"
	"time"
)

type Event[T any] struct {
	Event string `json:"event"`
	Data  T      `json:"data"`
}

type MessageHandler func(conn *websocket.Conn, id string, mt int, msg []byte)
type EventHandler[T any] func(conn *websocket.Conn, id string, mt int, msg Event[T])
type ConnectionHandler func(conn *websocket.Conn, id string)
type DisconnectionHandler func(conn *websocket.Conn, id string)

type Emitter struct {
	connections           map[string]*websocket.Conn
	connMux               sync.Mutex
	messageHandlers       []MessageHandler
	connectionHandlers    []ConnectionHandler
	disconnectionHandlers []DisconnectionHandler
	// handlersMux sync.Mutex
	// если будут траблы с гонками, то вернуть
}

func Broadcast[T any](e *Emitter, event Event[T]) []error {
	errs := []error{}
	for _, conn := range e.connections {
		err := Emit[T](conn, event.Event, event.Data)
		if err != nil {
			errs = append(errs, err)
		}
	}

	return errs
}

func Emit[T any](conn *websocket.Conn, event string, data T) error {
	ev := Event[T]{
		Event: event,
		Data:  data,
	}

	return conn.WriteJSON(ev)
}

func OnEvent[T any](e *Emitter, name string, handler EventHandler[T]) {
	e.OnMessage(func(conn *websocket.Conn, id string, mt int, msg []byte) {
		var event Event[T]
		if err := json.Unmarshal(msg, &event); err != nil {
			return
		}
		if event.Event != name {
			return
		}
		handler(conn, id, mt, event)
	})
}

func (e *Emitter) OnMessage(handler MessageHandler) {
	e.messageHandlers = append(e.messageHandlers, handler)
}

func (e *Emitter) GetConnection(id string) *websocket.Conn {
	e.connMux.Lock()
	defer e.connMux.Unlock()
	v, ok := e.connections[id]
	if ok {
		return v
	} else {
		return nil
	}
}

func (e *Emitter) OnConnection(handler ConnectionHandler) {
	e.connectionHandlers = append(e.connectionHandlers, handler)
}

func (e *Emitter) OnDisconnection(handler DisconnectionHandler) {
	e.disconnectionHandlers = append(e.disconnectionHandlers, handler)
}

func (e *Emitter) enterLoop(conn *websocket.Conn, id string) {
	defer func() {
		for _, event := range e.disconnectionHandlers {
			go event(conn, id)
		}
		conn.Close()
		e.connMux.Lock()
		defer e.connMux.Unlock()
		delete(e.connections, id)
	}()
	for {
		mt, msg, err := conn.ReadMessage()
		if err != nil {
			break
		}

		for _, handler := range e.messageHandlers {
			handler(conn, id, mt, msg)
		}
	}
}

func (e *Emitter) track(conn *websocket.Conn) {
	e.connMux.Lock()
	defer e.connMux.Unlock()
	var zeroTime time.Time
	conn.SetReadDeadline(zeroTime)
	id := uuid.NewString()
	e.connections[id] = conn
	go e.enterLoop(conn, id)
	go func() {
		for _, handler := range e.connectionHandlers {
			handler(conn, id)
		}
	}()
}

func newEmitter() fx.Option {
	return fx.Module("Emitter", fx.Provide(func() *Emitter {
		return &Emitter{
			connections: map[string]*websocket.Conn{},
		}
	}))
}
